package edu.ccut.saturn.component.impl.node;

import java.util.ArrayList;
import java.util.List;

import org.apache.commons.jexl.ExpressionFactory;

import edu.ccut.saturn.component.ComponentManager;
import edu.ccut.saturn.component.ILogicContext;
import edu.ccut.saturn.component.IParameterContext;
import edu.ccut.saturn.component.IParameterInfo;
import edu.ccut.saturn.component.SaturnDebugThreadsManager;
import edu.ccut.saturn.component.impl.SaturnParameterInfo;
import edu.ccut.saturn.component.loader.node.DecisionNodeInfoLoader;
import edu.ccut.saturn.component.loader.node.NodeInfoLoader;
import edu.ccut.saturn.component.utils.ExceptionUtils;
import edu.ccut.saturn.component.xml.ISaturnElement;
import edu.ccut.saturn.component.xml.SaturnElement;

/**
 * 判断结点的实现类。判断结点位于logic组件和action组件内部，通过对bool表达式的求值来决定流程运行时的下一个待执行结点。
 * <p>
 * <code>DecisionNodeInfo</code>扩展了<code>AbstractNodeInfo</code>:
 * <li>字段<code>expr</code>：表示判断结点需要判断的表达式;
 * <li>字段<code>then</code>，记录了当表达式为真时流程要运行的下一个结点的id；
 * <li>字段<code>elseId</code>，记录了当表达式为假时流程要运行的下一个结点的id。
 * 
 * @author 谷钰
 * @author 温建
 * 
 * @version 1.0 2008.8.19
 * 
 * @see AbstractNodeInfo
 * @see ILogicContext
 * 
 */
public class DecisionNodeInfo extends AbstractNodeInfo {

	private String expr;

	private String then;

	private String elseId;

	public DecisionNodeInfo(String id, String name, String description,
			String expr, String then, String elseId, String x, String y) {

		super(id, name, description, null, null, x, y, "decision");
		this.elseId = elseId;
		this.expr = expr;
		this.then = then;
	}

	/**
	 * 判断结点的执行方法。当表达式为空时，下一个运行结点是字段<code>elseId</code>
	 * 标识的结点；否则按照对表达式的求值为真时下一个运行结点是字段<code>then</code> 标识的结点，为假时是字段<code>elseId</code>标识的结点。
	 * <p>
	 * 为了保证线程安全，在判断表达式后需要将下一结点的信息保存到上下文的指定位置。
	 * 
	 * @param logicContext
	 *            逻辑上下文对象，记录结点运行时的上下文信息
	 * @throws Exception
	 */
	@Override
	public void doExecute(ILogicContext logicContext) throws Exception {
		ComponentManager.beforeExecuteComponent(this, this
				.createParameterContext(logicContext));

		try {
			Object result = "false";

			if (expr == null || expr.length() == 0) {
				logicContext.setNodeRouter(this.getId(), this.elseId);

			} else {
				result = ExpressionFactory.createExpression(expr).evaluate(
						logicContext.getJexlContext());

				if (result instanceof Boolean && (Boolean) result) {
					logicContext.setNodeRouter(this.getId(), this.then);

				} else {
					logicContext.setNodeRouter(this.getId(), this.elseId);
				}
			}

			logger.debug("      node : {execute : '{expression : '" + this.expr + "', result : '"
					+ result + "'}', process : '" + logicContext.getId() + "'}");

		} catch (Exception e) {
			logger.error("ERROR : {message : ' " + ExceptionUtils.getExMessage(e) + "' , process : '" + logicContext.getId() +"', node : '"
					+ super.getNodeType() + " " + super.getId() + " " + super.getName() + "'}" );
			throw e;
		} finally {
			ComponentManager.afterExecuteComponent(this,
					createParameterContext(logicContext));
		}
	}

	/**
	 * 获得判断结点的表达式
	 * 
	 * @return
	 */
	public String getExpr() {
		return expr;
	}

	/**
	 * 设置判断结点的表达式
	 * 
	 * @param expr
	 */
	public void setExpr(String expr) {
		this.expr = expr;
	}

	/**
	 * 获得当表达式为真时要执行的下一结点id
	 * 
	 * @return
	 */
	public String getThen() {
		return then;
	}

	/**
	 * 设置当表达式为真时要执行的下一结点id
	 * 
	 * @param expr
	 */
	public void setThen(String then) {
		this.then = then;
	}

	/**
	 * 获得当表达式为假时要执行的下一结点id
	 * 
	 * @return
	 */
	public String getElseId() {
		return elseId;
	}

	/**
	 * 设置当表达式为假时要执行的下一结点id
	 * 
	 * @param expr
	 */
	public void setElseId(String elseId) {
		this.elseId = elseId;
	}

	protected IParameterContext createParameterContext(
			ILogicContext logicContext) throws Exception {

		if (SaturnDebugThreadsManager.getInstance().isStart()) {
			initParameterInfos(logicContext);
			IParameterContext parameterContext = logicContext
					.createParameterContext(getParameterInfos());

			parameterContext.setParameter(3, ExpressionFactory
					.createExpression(expr).evaluate(
							logicContext.getJexlContext()));

			return parameterContext;
		}
		return null;
	}

	private void initParameterInfos(ILogicContext context) {
		List<IParameterInfo> parameters = new ArrayList<IParameterInfo>();
		SaturnParameterInfo exprParameter = new SaturnParameterInfo();
		exprParameter.setClassType(String.class);
		exprParameter.setId("1");
		exprParameter.setName("expression");
		exprParameter.setPassingType(IParameterInfo.PASSING_IN);
		exprParameter.setScope(IParameterInfo.SCOPE_LOGIC);
		exprParameter.setValueKey("'" + this.expr + "'");
		parameters.add(exprParameter);

		SaturnParameterInfo nextNodeParameter = new SaturnParameterInfo();
		nextNodeParameter.setClassType(String.class);
		nextNodeParameter.setId("2");
		nextNodeParameter.setName("trueNode");
		nextNodeParameter.setPassingType(IParameterInfo.PASSING_IN);
		nextNodeParameter.setScope(IParameterInfo.SCOPE_LOGIC);
		nextNodeParameter.setValueKey("'" + this.then + "'");
		parameters.add(nextNodeParameter);

		SaturnParameterInfo elseNodeParameter = new SaturnParameterInfo();
		elseNodeParameter.setClassType(String.class);
		elseNodeParameter.setId("3");
		elseNodeParameter.setName("falseNode");
		elseNodeParameter.setPassingType(IParameterInfo.PASSING_IN);
		elseNodeParameter.setScope(IParameterInfo.SCOPE_LOGIC);
		elseNodeParameter.setValueKey("'" + this.elseId + "'");
		parameters.add(elseNodeParameter);

		SaturnParameterInfo exprResultParameter = new SaturnParameterInfo();
		exprResultParameter.setClassType(Boolean.class);
		exprResultParameter.setId("4");
		exprResultParameter.setName("result");
		exprResultParameter.setPassingType(IParameterInfo.PASSING_IN_OUT);
		exprResultParameter.setScope(IParameterInfo.SCOPE_LOGIC);
		exprResultParameter.setValueKey(this.expr);
		parameters.add(exprResultParameter);
		super.setParameterInfos(parameters);
	}

	public ISaturnElement toSaturnElement() {
		ISaturnElement decisionElement = new SaturnElement(
				DecisionNodeInfoLoader.KEY_NODETYPE_DECISION);

		decisionElement.addAttribute(NodeInfoLoader.KEY_NODE_ID, this.getId());
		decisionElement.addAttribute(NodeInfoLoader.KEY_NODE_NEXT, this
				.getNextId());

		decisionElement.addAttribute(NodeInfoLoader.KEY_NODE_X, this.getX());
		decisionElement.addAttribute(NodeInfoLoader.KEY_NODE_Y, this.getY());

		ISaturnElement nameElement = new SaturnElement(
				NodeInfoLoader.KEY_NODE_NAME, this.getName());

		ISaturnElement parametersElement = new SaturnElement(
				NodeInfoLoader.KEY_PARAMETER_LIST);

		ISaturnElement descriptionElement = new SaturnElement(
				NodeInfoLoader.KEY_NODE_DESCRIPTION, this.getDescription());

		ISaturnElement ifElement = new SaturnElement(
				DecisionNodeInfoLoader.KEY_DECISION_IF);

		ifElement.addAttribute(DecisionNodeInfoLoader.KEY_DECISION_IF_THEN,
				this.then);

		ifElement.addAttribute(DecisionNodeInfoLoader.KEY_DECISION_IF_ELSE,
				this.elseId);

		ISaturnElement exprElement = new SaturnElement(
				DecisionNodeInfoLoader.KEY_DECISION_IF_EXPR, this.expr);

		ifElement.addSubElement(exprElement);
		decisionElement.addSubElement(nameElement);
		decisionElement.addSubElement(parametersElement);
		decisionElement.addSubElement(descriptionElement);
		decisionElement.addSubElement(ifElement);
		return decisionElement;
	}

}
